/*
*Copyright (C) 2007-2011 Borqs Ltd. All rights reserved.
*/

package com.borqs.music.lyric;

import com.borqs.music.R;
import com.borqs.music.util.MyLogger;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint.FontMetrics;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.SlidingDrawer;

public class LyricsView extends View {
    private final static MyLogger logger = MyLogger.getLogger(LyricsView.class.getSimpleName());
    // line spacing
    private final static int LINE_SPACING = 2;

    // duration of scrolling animation
    private final static int SCROLL_DURATION = 500;

    // frame interval
    private final static int SCROLL_INTERVAL = 50;

    public final static int LAUNCHED_BY_DEFAULT = 0;
    public final static int LAUNCHED_BY_WIDGET_3 = 1;

    private int mInitializeType = 0;

    // vertical margin
    private int mVerticalMargin;

    // lyrics object
    private Lyric mLyric;

    // visible physical line count
    private int mVisibleLineCount;

    // current lyrics position
    private int mCurrentLine;

    // dest lyric line to move to
    private List<Integer> mDestLines;

    // line height
    private int mLineHeight;

    // color of current line
    private int mFocusLineColor;

    // color of other lines
    private int mOtherLineColor;

    // flag indicating metrics need to be recalculated
    private boolean mNeedUpdateMetrics;

    // total lyrics line
    private int mLineCount;

    // text paint
    private TextPaint mPaint;

    // flag indicating we are performing scrolling
    private boolean mScrolling;

    // to control animation
    private long mAnimationStartTime;
    private int mAnimationDistance;

    private int mDirection = SCROLL_FOWARD;
    private static final int SCROLL_FOWARD = 0;
    private static final int SCROLL_BACK = 1;

    // no lyrics hint
    private String mNoLyricsHint;

    // count of breaked line of each lyrics line
    private int[] mBreakedLineCounts;

    // cache of all breaked line
    private Map<Integer, String[]> mBreakedLines;

    private SlidingDrawer mSlidingDrawer;

    public LyricsView(Context context) {
        this(context, null);
        init();
    }

    public LyricsView(Context context, int initializedBy) {
        this(context, null);
        mInitializeType = initializedBy;
        init();
    }

    public LyricsView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LyricsView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        // init model
        init();
    }

    private void init() {
        // init member
        mBreakedLines = new HashMap<Integer, String[]>();
        mDestLines = new ArrayList<Integer>();
        mNeedUpdateMetrics = true;
        mFocusLineColor = getResources().getColor(R.color.lyric_current_line_color);
        mOtherLineColor = getResources().getColor(R.color.lyric_other_line_color);

        // init paint
        mPaint = new TextPaint();
        mPaint.setAntiAlias(true);
        mPaint.setTextSize(getResources().getDimension(R.dimen.lyrics_font_size));
    }

    public void setLyrics(String lyrics) {
        if (lyrics == null) {
            mLyric = null;
            mLineCount = 0;
            mBreakedLineCounts = null;
        } else {
            mLyric = new Lyric(LyricUtils.getUTF8Bytes(lyrics), "utf-8");
            mLineCount = mLyric.getLineCount();
            mBreakedLineCounts = new int[mLineCount];
        }

        // reset current line
        mCurrentLine = 0;

        // clear line cache
        mBreakedLines.clear();

        invalidate();
    }

    public void setLyrics(String lyrics, int pos) {
        if (lyrics == null) {
            mLyric = null;
            mLineCount = 0;
            mBreakedLineCounts = null;
        } else {
            mLyric = new Lyric(LyricUtils.getUTF8Bytes(lyrics), "utf-8");
            mLineCount = mLyric.getLineCount();
            mBreakedLineCounts = new int[mLineCount];
        }

        // reset current line
        logger.d("setLyrics pos="+pos);
        seekByTime(pos, false);

        // clear line cache
        mBreakedLines.clear();

        invalidate();
    }

    public boolean hasLyrics() {
        return mLyric != null;
    }

    public void seekByTime(long time, boolean animate) {
        if (mLyric != null) {
            mLyric.seekByTime(time);
            int index = mLyric.getCurrentIndex();
            setCurrentLine(index, animate);
        }
    }

    public int getNextTime() {
        return mLyric == null ? -1 : mLyric.getNextTime();
    }

    public void reset() {
        synchronized (mDestLines) {
            mDestLines.clear();
        }

        mScrolling = false;
        mCurrentLine = 0;
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // get size
        int width = getWidth();
//        int height = getHeight();
        int height = mSlidingDrawer.getHeight() - mSlidingDrawer.getHandle().getBottom();

        // set a clip
        canvas.save();
        canvas.clipRect(getPaddingLeft(), getPaddingTop(), width - getPaddingRight(), height - getPaddingBottom());

        if (mInitializeType == LAUNCHED_BY_WIDGET_3) {
            canvas.scale(-1.0f, 1.0f, getWidth() / 2, getHeight() / 2);
        }

        // get font metrics
        FontMetrics fm = mPaint.getFontMetrics();

        // get max line if not get yet
        if (mNeedUpdateMetrics)
            calculateMetrics();

        // if null, draw a hint
        if (mLyric == null) {
            if (!TextUtils.isEmpty(mNoLyricsHint)) {
                mPaint.setColor(mOtherLineColor);
                mPaint.setAlpha(255);

                String[] lines = LyricUtils.breakLine(mNoLyricsHint, mPaint, width - getPaddingLeft() - getPaddingRight());
                int y = (height - mLineHeight * lines.length - LINE_SPACING * (lines.length - 1)) / 2;
                y -= fm.ascent;
                for (String line : lines) {
                    int textWidth = (int) mPaint.measureText(line);
                    int x = Math.max(0, (width - textWidth) / 2);
                    canvas.drawText(line, x, y, mPaint);
                    y += mLineHeight + LINE_SPACING;
                }
            }

            return;
        }

        // get max vertical location, if exceed, no need draw
        int maxVertical = height - getPaddingBottom();

        // do different according to scrolling flag
        if (mScrolling) {
            // calculate offset
            long now = System.currentTimeMillis();
            int offsetY = (int) (mAnimationDistance * (float) (now - mAnimationStartTime) / SCROLL_DURATION);
            offsetY = Math.min(offsetY, mAnimationDistance);

            // calculate alpha
            int alpha = (int)(255 * (float)offsetY / mAnimationDistance);

            // draw other lines
            int y = mVerticalMargin - (int) fm.ascent + getPaddingTop();
            int startLine = mCurrentLine;
            int upLines = mVisibleLineCount / 2;
            while (upLines > 0) {
                startLine--;
                if (startLine >= 0 && startLine < mBreakedLineCounts.length)
                    upLines -= mBreakedLineCounts[startLine];
                else
                    upLines--;
            }
            if (upLines < 0)
                y -= (mLineHeight + LINE_SPACING) * Math.abs(upLines);
            for (int i = startLine; i < mLineCount && y < maxVertical; i++) {
                if (i < 0) {
                    y += mLineHeight + LINE_SPACING;
                    continue;
                }

                // choose color
                if (i == mCurrentLine)
                    mPaint.setColor(mFocusLineColor);
                else
                    mPaint.setColor(mOtherLineColor);

                // choose alpha
                if (i == mCurrentLine)
                    mPaint.setAlpha(255 - alpha);
                else
                    mPaint.setAlpha(255);

                // break lines
                if (mBreakedLineCounts[i] == 0)
                    breakLyricsLine(i);

                // draw all broken lines
                if (SCROLL_FOWARD == mDirection) {
                    y = drawLine(canvas, i, y, offsetY);
                } else {
                	y = drawLine(canvas, i, y, -offsetY);
                }
            }

            // check animation state
            if (offsetY < mAnimationDistance) {
                postInvalidateDelayed(SCROLL_INTERVAL);
            } else {
                boolean more = false;
                synchronized (mDestLines) {
                    // set current line
                    mCurrentLine = mDestLines.remove(0);

                    if (SCROLL_FOWARD == mDirection) {
                        // purge smaller lines
                        while (!mDestLines.isEmpty()) {
                            int line = mDestLines.get(0);
                            if (line <= mCurrentLine)
                                mDestLines.remove(0);
                            else
                                break;
                        }
                    } else {
                    	// purge latter lines
                        while (!mDestLines.isEmpty()) {
                            int line = mDestLines.get(0);
                            if (line >= mCurrentLine)
                                mDestLines.remove(0);
                            else
                                break;
                        }
                    }

                    // check it there more dest lines
                    more = !mDestLines.isEmpty();
                    if (more) {
                        int line = mDestLines.get(0);

                        // ensure those line is broken
                        int lineCount = 0;
                        int low, high;
                        if (mCurrentLine < line) {
                        	low = mCurrentLine;
                        	high = line;
                        } else {
                        	low = line;
                        	high = mCurrentLine;
                        }
                        for (int i = low; i < high; i++) {
                            if (mBreakedLineCounts[i] == 0)
                                breakLyricsLine(i);

                            lineCount += mBreakedLineCounts[i];
                        }

                        // get correct animation distance
                        mAnimationDistance = lineCount * (mLineHeight + LINE_SPACING);
                        mScrolling = true;
                        mAnimationStartTime = System.currentTimeMillis();
                    } else {
                        mScrolling = false;
                    }
                    invalidate();
                }
            }
        } else {
            // draw lines from start, need skip line number which is less than zero
            int y = mVerticalMargin - (int) fm.ascent + getPaddingTop();
            int startLine = mCurrentLine;
            int upLines = mVisibleLineCount / 2;
            while (upLines > 0) {
                startLine--;
                if (startLine >= 0 && startLine < mBreakedLineCounts.length) {
                    if (0 == mBreakedLineCounts[startLine]) {
                        breakLyricsLine(startLine);
                    }
                    upLines -= mBreakedLineCounts[startLine];
                }
                else
                    upLines--;
            }
            if (upLines < 0)
                y -= (mLineHeight + LINE_SPACING) * Math.abs(upLines);
            for (int i = startLine; i < mLineCount && y < maxVertical; i++) {
                if (i < 0) {
                    y += mLineHeight + LINE_SPACING;
                    continue;
                }

                // choose color
                if (i == mCurrentLine)
                    mPaint.setColor(mFocusLineColor);
                else
                    mPaint.setColor(mOtherLineColor);

                // break lines
                if (mBreakedLineCounts[i] == 0)
                    breakLyricsLine(i);

                // draw all broken lines
                y = drawLine(canvas, i, y, 0);
            }
        }

        // restore canvas
        canvas.restore();
    }

    private void breakLyricsLine(int index) {
        int maxWidth = getWidth() - getPaddingLeft() - getPaddingRight();
        String line = mLyric.getLineByIndex(index);
        String[] lineArray = LyricUtils.breakLine(line, mPaint, maxWidth);

        // save line count
        mBreakedLineCounts[index] = lineArray.length;

        // convert to string array
        mBreakedLines.put(index, lineArray);
    }

    private int drawLine(Canvas canvas, int index, int y, int offset) {
        int width = getWidth() - getPaddingLeft() - getPaddingRight();
        String[] lines = mBreakedLines.get(index);
        for (String line : lines) {
            int textWidth = (int) mPaint.measureText(line);
            int x = (width - textWidth) / 2 + getPaddingLeft();
            canvas.drawText(line, x, y - offset, mPaint);
            y += mLineHeight + LINE_SPACING;
        }
        return y;
    }

    private void calculateMetrics() {
        int totalHeight = mSlidingDrawer.getHeight() - mSlidingDrawer.getHandle().getBottom();
        int height = totalHeight - getPaddingBottom() - getPaddingTop();
        FontMetrics fm = mPaint.getFontMetrics();
        mLineHeight = (int) (fm.descent - fm.ascent);
        mVisibleLineCount = (height + LINE_SPACING) / (mLineHeight + LINE_SPACING);
        mVerticalMargin = ((height + LINE_SPACING) % (mLineHeight + LINE_SPACING)) / 2;
        mNeedUpdateMetrics = false;
    }

    public void setCurrentLine(int line, boolean animate) {
    	mDirection = SCROLL_FOWARD;

    	if (line == mCurrentLine) {
    	    return;
    	}

        if (animate) {
            synchronized (mDestLines) {
                mDestLines.add(line);

                if (!mScrolling) {
                    // ensure those line is broken
                    int lineCount = 0;
                    int high, low;
                    if (line > mCurrentLine) {
                    	high = line;
                    	low = mCurrentLine;
                    } else {
                    	low = line;
                    	high = mCurrentLine;
                    }
                    try {
                    for (int i = low; i < high; i++) {
                        if (mBreakedLineCounts[i] == 0)
                            breakLyricsLine(i);

                        lineCount += mBreakedLineCounts[i];
                    }
                    } catch (Exception e) {
                    	e.printStackTrace();
                    	logger.e("line=" + line + "mCurrentLine=" + mCurrentLine
                    			+ " mBreakedLineCounts.length=" + mBreakedLineCounts.length
                    			+ "mLyric.getLineCount=" + mLyric.getLineCount());
                    }

                    // get correct animation distance
                    mAnimationDistance = lineCount * (mLineHeight + LINE_SPACING);
                    mScrolling = true;
                    mAnimationStartTime = System.currentTimeMillis();
                }
                if (line < mCurrentLine) {
                	mDirection = SCROLL_BACK;
                }
            }
        } else {
            mCurrentLine = line;
        }
        invalidate();
    }

    public int getFocusLineColor() {
        return mFocusLineColor;
    }

    public void setFocusLineColor(int focusLineColor) {
        mFocusLineColor = focusLineColor;
    }

    public int getOtherLineColor() {
        return mOtherLineColor;
    }

    public void setOtherLineColor(int otherLineColor) {
        mOtherLineColor = otherLineColor;
    }

    public void setTextSize(float textSize) {
        mPaint.setTextSize(textSize);
        calculateMetrics();
        invalidate();
    }

    public String getNoLyricsHint() {
        return mNoLyricsHint;
    }

    public void setNoLyricsHint(String noLyricsHint) {
        mNoLyricsHint = noLyricsHint;
    }

    public void setSlidingDrawer(SlidingDrawer slidingDrawer) {
        mSlidingDrawer = slidingDrawer;
    }

}